﻿// MainMenu tutorial I followed http://www.youtube.com/watch?v=54L_w0PiRa8
// Collision code tutorial I followed http://www.youtube.com/watch?v=TlHSNjeND9s

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Santa_sAdventure.Model;
using System.IO;
using Santa_sAdventure.View;
using Santa_sAdventure.Controller.Enums;
using Santa_sAdventure.Controller.Buttons;
using Santa_sAdventure.Model.Enums;
using Santa_sAdventure.Controller;

namespace Santa_sAdventure
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class MasterController : Microsoft.Xna.Framework.Game
    {
        // Draw
        GraphicsDeviceManager m_graphics;
        SpriteBatch m_spriteBatch;
        // Level
        private int m_numberOfLevels = 0;
        private int m_levelIndex = -1;
        private Level m_level = null;
        // Classes
        private GameView m_gameView;
        private Camera m_camera;
        private Model.Model m_model;
        // States
        private GameState m_gameState = GameState.MainMenu;
        // Menu btn
        private MenuButton m_playBtn;
        private MenuButton m_resumeBtn;
        // ----Images----
        // Backgrounds
        private Texture2D m_menuBackgroundTexture;
        private Texture2D m_menuBackgroundPausedTexture;
        // Effects
        private Texture2D m_sparkTexture;
        List<BulletExplosionView> m_bulletExplosionSystems = new List<BulletExplosionView>();
        // Buttons
        private Texture2D m_playButtonTexture;
        private Texture2D m_resumeButtonTexture;
        // Sounds
        SoundEffect m_dieSound;

        public MasterController()
        {
            m_graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {

            m_graphics.PreferredBackBufferWidth = 1024;
            m_graphics.PreferredBackBufferHeight = 760;
            m_graphics.ApplyChanges();

            // Create a new SpriteBatch, which can be used to draw textures.
            m_spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here

            // Background Snow particles
            Texture2D snowParticleTexture = Content.Load<Texture2D>("Images/Particles/Snow/snow");

            // Initialize classes
            m_camera = new Camera(m_graphics.PreferredBackBufferWidth, m_graphics.PreferredBackBufferHeight, 20);
            m_gameView = new GameView(m_camera, snowParticleTexture, Content);
            m_model = new Model.Model(Content);

            // Load images
            m_menuBackgroundTexture = Content.Load<Texture2D>("Images/Backgrounds/background");
            m_menuBackgroundPausedTexture = Content.Load<Texture2D>("Images/Backgrounds/background-paused");
            m_playButtonTexture = Content.Load<Texture2D>("Images/Buttons/playButton");
            m_resumeButtonTexture = Content.Load<Texture2D>("Images/Buttons/resumeButton");
            m_sparkTexture = Content.Load<Texture2D>("Images/Particles/Spark/spark");

            // Make menu btn
            m_playBtn = new MenuButton(m_playButtonTexture, new Vector2(m_graphics.PreferredBackBufferWidth / 2, m_graphics.PreferredBackBufferHeight / 2),
                                       m_graphics.PreferredBackBufferWidth, m_graphics.PreferredBackBufferHeight);
            m_resumeBtn = new MenuButton(m_resumeButtonTexture, new Vector2(m_graphics.PreferredBackBufferWidth / 2, m_graphics.PreferredBackBufferHeight / 2),
                                       m_graphics.PreferredBackBufferWidth, m_graphics.PreferredBackBufferHeight);

            // Check how many levels there are
            string[] files = Directory.GetFiles("Content/Levels");
            m_numberOfLevels = files.Count();

            // Make mousepointer visible
            IsMouseVisible = true;            

            LoadNextLevel();

            // Load sounds
            MediaPlayer.IsRepeating = true;
            MediaPlayer.Volume = 0.3f;
            MediaPlayer.Play(Content.Load<Song>("Sounds/Music/background-music"));
            m_dieSound = Content.Load<SoundEffect>("Sounds/Events/die");

        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            // Get mouseState
            MouseState mouseState = Mouse.GetState();

            // Check gamestate
            switch (m_gameState)
            {
                // If at mainMenu, check btn
                case GameState.MainMenu:
                    m_playBtn.Update(mouseState.LeftButton == ButtonState.Pressed, mouseState.X, mouseState.Y);
                    if (m_playBtn.IsClicked)
                    {
                        m_gameState = GameState.Playing;
                    }
                    break;
                // If playing, update play elements
                case GameState.Playing:
                    if (!m_level.IsCompleted)
                    {
                        if (m_model.GetPlayer().IsALive())
                        {
                            // Mousepointer invisible
                            IsMouseVisible = false;

                            var oldPosition = m_model.GetPlayer().Position;
                            // Check input
                            if (m_gameView.DidPlayerJump())
                            {
                                m_model.IsJumpPossible();
                            }
                            else if (m_gameView.DidPlayerPressLeft())
                            {
                                m_model.MovePlayerLeft((float)gameTime.ElapsedGameTime.TotalSeconds);
                            }
                            else if (m_gameView.DidPlayerPressRight())
                            {
                                m_model.MovePlayerRight((float)gameTime.ElapsedGameTime.TotalSeconds);
                            }
                            else
                            {
                                m_model.StandStill();
                            }


                            // Check Player Collision
                            isColliding(oldPosition);                            

                            // Update model
                            m_model.Update((float)gameTime.ElapsedGameTime.TotalSeconds);

                            // Update all the particles
                            for (int i = 0; i < m_bulletExplosionSystems.Count(); i++)
                            {
                                m_bulletExplosionSystems[i].Update((float)gameTime.ElapsedGameTime.TotalSeconds);
                            }

                            // If player pressed pause btn
                            if (m_gameView.DidPlayerPause())
                            {
                                // Make mousepointer visible
                                IsMouseVisible = true;

                                m_gameState = GameState.Paused;
                            }
                        }
                        else 
                        {
                            // Make mousepointer visible
                            IsMouseVisible = true;

                            // Continue btn
                            m_resumeBtn.Update(mouseState.LeftButton == ButtonState.Pressed, mouseState.X, mouseState.Y);
                            // If btn is pressed
                            if (m_resumeBtn.IsClicked)
                            {
                                if (m_model.GetPlayer().NoLifesLeft)
                                {
                                    // Player has no lifes left, reload level
                                    ReloadLevel();
                                }
                                // Respawn player
                                m_model.GetPlayer().Respawn();
                            }                            
                        }
                    }
                    else
                    {
                        // Make mousepointer visible
                        IsMouseVisible = true;

                        // Player has won
                        // Continue btn
                        m_resumeBtn.Update(mouseState.LeftButton == ButtonState.Pressed, mouseState.X, mouseState.Y);
                        // If btn is pressed
                        if (m_resumeBtn.IsClicked)
                        {
                            LoadNextLevel();
                            // Reset btn position
                            m_resumeBtn.ResetPosition();
                        }   
                    }
                    
                    break;
                // If paused, check btn
                case GameState.Paused:
                    m_resumeBtn.Update(mouseState.LeftButton == ButtonState.Pressed, mouseState.X, mouseState.Y);
                    if (m_resumeBtn.IsClicked)
                    {
                        m_gameState = GameState.Playing;
                    }
                    break;
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            m_spriteBatch.Begin();
            switch (m_gameState)
            {
                // If MainMenu draw menu stuff
                case GameState.MainMenu:
                    // Background
                    m_spriteBatch.Draw(m_menuBackgroundTexture, new Rectangle(0, 0, m_graphics.PreferredBackBufferWidth, m_graphics.PreferredBackBufferHeight), Color.White);
                    // Buttons
                    m_playBtn.Draw(m_spriteBatch);
                    break;
                // If playing draw play elements
                case GameState.Playing:
                    if (!m_level.IsCompleted)
                    {
                        m_gameView.DrawLevel(m_level, m_spriteBatch, Content, m_model.GetPlayer(), (float)gameTime.ElapsedGameTime.TotalSeconds);

                        // Draw all existing particles
                        for (int i = 0; i < m_bulletExplosionSystems.Count(); i++)
                        {
                            m_bulletExplosionSystems[i].Draw((float)gameTime.ElapsedGameTime.TotalSeconds);
                        }

                        if (m_model.GetPlayer().IsALive() || m_gameView.DidPlayerContinue())
                        {                                                     
                        }
                        else
                        {
                            // Player Died
                            if (m_model.GetPlayer().NoLifesLeft)
                            {
                                // player has no lifes left!                                
                                m_gameView.DrawResumeBtn(m_resumeBtn, m_spriteBatch);
                                Vector2 position = new Vector2(5, 6);
                                m_gameView.DrawText("No Lifes Left!", position, m_spriteBatch, 2);
                            }
                            else
                            {
                                // Player has lifes left                                
                                m_gameView.DrawResumeBtn(m_resumeBtn, m_spriteBatch);
                                Vector2 position = new Vector2(5, 6);
                                m_gameView.DrawText("You died :(", position, m_spriteBatch, 2);
                            }

                        }
                        
                    }
                    else
                    {
                        // Player Won                        
                        var btnRec = m_camera.GetVisualRectangle(m_level.Width/2, m_level.Height / 2 + 2, 1,1);
                        m_resumeBtn.Position = new Vector2(m_resumeBtn.Position.X, btnRec.Y);
                        m_resumeBtn.Draw(m_spriteBatch);
                        // Level stats
                        m_gameView.DrawLevelStats(m_level, m_spriteBatch, Content, m_model.GetPlayer());   
                    }
                   
                    break;
                // If Paused draw paused menu stuff
                case GameState.Paused:
                    m_spriteBatch.Draw(m_menuBackgroundPausedTexture, new Rectangle(0, 0, m_graphics.PreferredBackBufferWidth, m_graphics.PreferredBackBufferHeight), Color.White);
                    // Buttons
                    m_gameView.DrawResumeBtn(m_resumeBtn, m_spriteBatch);
                    break;
            }
            m_spriteBatch.End();
            base.Draw(gameTime);
        }

        // Load next level
        private void LoadNextLevel()
        {
            // move to the next level
            m_levelIndex = (m_levelIndex + 1) % m_numberOfLevels;

            // Unloads the content for the current level before loading the next one.
            if (m_level != null)
                m_level.Dispose();

            // Load the level
            m_level = m_model.GetNewLevel(LevelImporter.GetLevelString(m_levelIndex), m_levelIndex);
        }

        // Loads the same level
        private void ReloadLevel()
        {
            // Load same level
            m_level = m_model.GetNewLevel(LevelImporter.GetLevelString(m_levelIndex), m_levelIndex);
        }

        // Check colliders
        private bool isColliding(Vector2 a_oldPosition)
        {
            var player = m_model.GetPlayer();
            var enemies = m_model.GetEnemies();
            var tiles = m_level.GetTiles();
            var packets = m_level.GetPackets();

            //Player rectangle
            Rectangle playerRec = m_camera.GetVisualRectangle(player.Position.X, player.Position.Y,
                                                                               player.Size.X, player.Size.Y);

            //Enemy rec
            if (enemies != null)
            {
                for (int i = 0; i < enemies.Count; i++)
                {
                    // Check if enemy is dead
                    if (!enemies[i].IsDead)
                    {
                        Rectangle enemyRec = m_camera.GetVisualRectangle(enemies[i].Position.X, enemies[i].Position.Y,
                                                 enemies[i].Size.X, enemies[i].Size.Y);
                        // Check if player intersect with enemy
                        if (playerRec.TopCollision(enemyRec))
                        {
                            // Player jumps on enemy head, kill it
                            enemies[i].KillEnemy();
                            m_level.IncreaseEnemiesKilled();
                            player.CanJump = true;
                            player.Jump();
                            m_dieSound.Play();
                        }
                        else if (playerRec.Intersects(enemyRec))
                        {
                            // Kill player
                            player.KillPlayer();
                            m_dieSound.Play();
                        }

                        // Check enemy position
                        if (enemies[i].Velocity.X > 0 &&
                            tiles[(int)(enemies[i].Position.X + enemies[i].Size.X), (int)(enemies[i].Position.Y + enemies[i].Size.Y)].GetTileType() == TileType.Passable ||
                            tiles[(int)(enemies[i].Position.X + enemies[i].Size.X), (int)(enemies[i].Position.Y)].GetTileType() == TileType.Solid || // Going left
                            enemies[i].Velocity.X < 0 &&
                            tiles[(int)(enemies[i].Position.X), (int)(enemies[i].Position.Y + enemies[i].Size.Y)].GetTileType() == TileType.Passable ||
                            tiles[(int)(enemies[i].Position.X), (int)(enemies[i].Position.Y)].GetTileType() == TileType.Solid // Going right
                            )
                        {
                            enemies[i].ChangeDirection();
                        }

                        // Check enemy bullets
                        if (enemies[i] is ShootingEnemy)
                        {
                            var bullets = (enemies[i] as ShootingEnemy).GetAllBullets;
                            for (int j = 0; j < bullets.Count; j++)
                            {
                                // Bullet rec
                                Rectangle bulletRec = m_camera.GetVisualRectangle(bullets[j].Position.X, bullets[j].Position.Y, bullets[j].Size.X, bullets[j].Size.Y);
                                for (int x = 0; x < m_level.Width; x++)
                                {
                                    for (int y= 0; y < m_level.Height; y++)
                                    {
                                        if (tiles[x,y].GetTileType() == TileType.Solid)
                                        {                                          
                                            // Tile rec
                                            Rectangle tileRec = m_camera.GetVisualRectangle(x, y, 1, 1);

                                            // Check if bullet hit any wall
                                            if (bulletRec.BottomCollision(tileRec))
                                            {
                                                float xPosition = (float)Math.Round(bullets[j].Position.X);
                                                float yPosition = bullets[j].Position.Y;
                                                BulletExplosionView bulletexplosion = new BulletExplosionView(m_spriteBatch, m_sparkTexture, m_camera);
                                                // Determine wich way the particles will fly
                                                if (x == xPosition)
                                                {
                                                    // Left
                                                    bulletexplosion.SetSystemStartPositions(xPosition, yPosition, 1);
                                                }
                                                else
                                                {
                                                    // Right
                                                    bulletexplosion.SetSystemStartPositions(xPosition, yPosition, -1);
                                                }
                                                m_bulletExplosionSystems.Add(bulletexplosion);
                                                
                                                (enemies[i] as ShootingEnemy).KillBullet(j);
                                            }
                                        }
                                    }
                                }
                                // Check if bullet hit player
                                if (player.IsALive())
                                {
                                    // Check if player intersect with bullet
                                    if (playerRec.TopCollision(bulletRec))
                                    {
                                        // Player jumps on bullet, kill it
                                        (enemies[i] as ShootingEnemy).KillBullet(j);
                                        player.CanJump = true;
                                        player.Jump();
                                    }
                                    else if (playerRec.Intersects(bulletRec))
                                    {
                                        // Kill player
                                        player.KillPlayer();
                                        m_dieSound.Play();
                                        RemoveAllBullets();
                                    }
                                }      
                            }
                        }
                    }
                }
            }
            
            // Packet rec
            if (packets != null)
            {
                for (int i = 0; i < packets.Count; i++)
                {
                    Rectangle packetRec = m_camera.GetVisualRectangle(packets[i].Position.X, packets[i].Position.Y,
                                                                     packets[i].Size.X, packets[i].Size.Y);
                    // Check if player intersect with packet
                    if (playerRec.Intersects(packetRec))
                    {
                        if (!packets[i].IsTaken)
                        {
                            packets[i].IsTaken = true;
                            m_level.IncreasePacketsTaken(); 
                        }                        
                    }
                }
            }

            // Exit rec
            Rectangle exitRec = m_camera.GetVisualRectangle(m_level.ExitPosition.X, m_level.ExitPosition.Y - 1, 1, 2);
            // If player reaches exit
            if (playerRec.Intersects(exitRec))
            {
                m_level.IsCompleted = true;
            }
            else
            {
                // Player cannot jump unless standing on solid ground
                player.CanJump = false;
                for (int x = (int)player.Position.X - 2; x < player.Position.X + 3; x++)
                {
                    for (int y = (int)player.Position.Y - 3; y < player.Position.Y + 2; y++)
                    {
                        // Check so x and y are inside the map
                        if (x < 20 && x >= 0 && y < 20 && y >= 0)
                        {
                            // Rectangles
                            Rectangle tileRec = m_camera.GetVisualRectangle(x, y, 1, 1);

                            if (tiles[x, y].GetTileType() == TileType.Trap)
                            {
                                if (playerRec.TopCollision(tileRec))
                                {
                                    player.KillPlayer();
                                    m_dieSound.Play();
                                }   
                            }
                            else if (tiles[x, y].GetTileType() == TileType.Solid)
                            {    
                                if (playerRec.LeftCollision(tileRec))
                                {
                                    // Collide from left
                                    if (Math.Floor(a_oldPosition.X) == x)
                                    {
                                        player.Position = new Vector2((float)Math.Ceiling(a_oldPosition.X), a_oldPosition.Y);

                                    }
                                    else
                                    {
                                        player.Position = a_oldPosition;
                                    }
                                }
                                if (playerRec.RightCollision(tileRec))
                                {
                                    // Collide from right
                                    if (Math.Floor(a_oldPosition.X + 1) == x)
                                    {
                                        float difference = 1 - player.Size.X;
                                        player.Position = new Vector2((float)Math.Floor(a_oldPosition.X) + difference, a_oldPosition.Y);

                                    }
                                    else
                                    {
                                        player.Position = a_oldPosition;
                                    }
                                }
                                if (playerRec.TopCollision(tileRec))
                                {
                                    // Solid ground player can jump
                                    player.CanJump = true;
                                    if (player.Position.Y + player.Size.Y > y)
                                    {
                                        // Position is invalid, make it right
                                        if (a_oldPosition.Y + player.Size.Y > y)
                                        {
                                            // oldposition is wrong aswell
                                            float difference = 2 - player.Size.Y;
                                            player.Position = new Vector2(player.Position.X, (float)Math.Floor(a_oldPosition.Y) + difference);
                                        }
                                        else
                                        {
                                            // oldposition is valid
                                            player.Position = new Vector2(player.Position.X, a_oldPosition.Y);
                                        }

                                    }

                                    // Stop Y movement
                                    if (player.Velocity.Y != player.JumpSpeed)
                                    {
                                        player.Velocity = new Vector2(player.Velocity.X, 0);
                                    }

                                    player.CanJump = true;
                                }
                                if (playerRec.BottomCollision(tileRec))
                                {
                                    // Collide from bottom
                                    player.Velocity = new Vector2(player.Velocity.X, 1);
                                }
                            }
                        }
                    }
                }
            }
            
            return false;
        }

        // Removes all the bullets in game
        private void RemoveAllBullets()
        {
            var enemyList = m_level.GetEnemies();
            for (int i = 0; i < enemyList.Count; i++)
            {
                if (enemyList[i] is ShootingEnemy)
                {
                    var bulletList = (enemyList[i] as ShootingEnemy).GetAllBullets;
                    for (int j = 0; j < bulletList.Count; j++)
                    {
                        (enemyList[i] as ShootingEnemy).KillBullet(j);
                    }
                } 
            }
        }
    }



}


// Class to determine collision with two rectangles
static class RectangleCollision
{
    const int margin = 10;

    // Collide from top
    public static bool TopCollision(this Rectangle rec1, Rectangle rec2)
    {
        return (rec1.Bottom >= rec2.Top - 1 &&
                rec1.Bottom <= rec2.Top + margin &&
                rec1.Right >= rec2.Left + margin &&
                rec1.Left <= rec2.Right - margin);
    }
    
    // Collide from Bottom
    public static bool BottomCollision(this Rectangle rec1, Rectangle rec2)
    {
         return (rec1.Top >= rec2.Bottom - margin &&
                 rec1.Top <= rec2.Bottom + 1 &&
                 rec1.Right >= rec2.Left + margin &&
                 rec1.Left <= rec2.Right - margin);
    }

    // Collide from Right
    public static bool RightCollision(this Rectangle rec1, Rectangle rec2)
    {
        return (rec1.Right >= rec2.Left - 1 &&
                rec1.Right <= rec2.Left + margin &&
                rec1.Bottom >= rec2.Top + margin &&
                rec1.Top <= rec2.Bottom - margin);
    }

    // Collide from Left
    public static bool LeftCollision(this Rectangle rec1, Rectangle rec2)
    {

        return (rec1.Left >= rec2.Right - margin &&
                rec1.Left <= rec2.Right + 1 &&
                rec1.Bottom >= rec2.Top + margin &&
                rec1.Top <= rec2.Bottom - margin);
    }
}